/* DIME IME for Windows 7/8/10/11

BSD 3-Clause License

Copyright (c) 2022, Jeremy Wu
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
   contributors may be used to endorse or promote products derived from
   this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

//#define DEBUG_PRINT
#include "Private.h"
#include "Globals.h"
#include "BaseWindow.h"
#include "ScrollBarWindow.h"

//////////////////////////////////////////////////////////////////////
//
// CScrollBarWindowFactory class.
//
//////////////////////////////////////////////////////////////////////
CScrollBarWindowFactory* CScrollBarWindowFactory::_instance;

CScrollBarWindowFactory::CScrollBarWindowFactory()
{
    _instance = nullptr;
}

CScrollBarWindowFactory* CScrollBarWindowFactory::Instance()
{
    if (nullptr == _instance)
    {
        _instance = new (std::nothrow) CScrollBarWindowFactory();
    }

    return _instance;
}

CScrollBarWindow* CScrollBarWindowFactory::MakeScrollBarWindow(SHELL_MODE shellMode)
{
    CScrollBarWindow* pScrollBarWindow = nullptr;

    switch (shellMode)
    {
    case STOREAPP:
        pScrollBarWindow = new (std::nothrow) CScrollBarWindow();
        break;

    case DESKTOP:
		pScrollBarWindow = new (std::nothrow) CScrollBarWindow();
        //pScrollBarWindow = new (std::nothrow) CScrollBarNullWindow();
        break;

    default:
        pScrollBarWindow = new (std::nothrow) CScrollBarWindow();
		//pScrollBarWindow = new (std::nothrow) CScrollBarNullWindow();
        break;
    }
    return pScrollBarWindow;
}

void CScrollBarWindowFactory::Release()
{
    if (_instance)
    {
        delete _instance;
        _instance = nullptr;
    }
}

//////////////////////////////////////////////////////////////////////
//
// CScrollBarWindow class.
//
//////////////////////////////////////////////////////////////////////+---------------------------------------------------------------------------
//
// ctor
//
//----------------------------------------------------------------------------

CScrollBarWindow::CScrollBarWindow()
{
    _pBtnUp = nullptr;
    _pBtnDn = nullptr;

    _scrollDir = SCROLL_NONE_DIR;

    _sizeOfScrollBtn.cx = GetSystemMetrics(SM_CXVSCROLL) * 3/ 2;
    _sizeOfScrollBtn.cy = GetSystemMetrics(SM_CYVSCROLL) * 3/ 2;
}

//+---------------------------------------------------------------------------
//
// dtor
//
//----------------------------------------------------------------------------

CScrollBarWindow::~CScrollBarWindow()
{
    if (_pBtnUp)
    {
        delete _pBtnUp;
        _pBtnUp = nullptr;
    }
    if (_pBtnDn)
    {
        delete _pBtnDn;
        _pBtnDn = nullptr;
    }
}

//+---------------------------------------------------------------------------
//
// _Create
//
//----------------------------------------------------------------------------

BOOL CScrollBarWindow::_Create(ATOM atom, DWORD dwExStyle, DWORD dwStyle, CBaseWindow *pParent, int wndWidth, int wndHeight)
{
	debugPrint(L"CScrollBarWindow::_Create(), gdiObjects = %d", GetGuiResources(GetCurrentProcess(), GR_GDIOBJECTS));
    if (!CBaseWindow::_Create(atom, dwExStyle, dwStyle, pParent, wndWidth, wndHeight))
    {
        return FALSE;
    }

    _pBtnUp = new (std::nothrow) CScrollButtonWindow(dwStyle & WS_HSCROLL ? DFCS_SCROLLLEFT : DFCS_SCROLLUP);
    if (_pBtnUp == nullptr)
    {
        return FALSE;
    }

    _pBtnUp->_Create(NULL, 0, 0, this);
    _pBtnUp->_SetUIWnd(_GetUIWnd());

    _pBtnDn = new (std::nothrow) CScrollButtonWindow(dwStyle & WS_HSCROLL ? DFCS_SCROLLRIGHT : DFCS_SCROLLDOWN);
    if (_pBtnDn == nullptr)
    {
        delete _pBtnUp;
        _pBtnUp = nullptr;
        return FALSE;
    }

    _pBtnDn->_Create(NULL, 0, 0, this);
    _pBtnDn->_SetUIWnd(_GetUIWnd());

    return TRUE;
}

//+---------------------------------------------------------------------------
//
// _WindowProcCallback
//
// Scrollbar window proc.
//----------------------------------------------------------------------------

LRESULT CALLBACK CScrollBarWindow::_WindowProcCallback(_In_ HWND wndHandle, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam)
{
	
    switch (uMsg)
    {
    case WM_PAINT:
        {
			debugPrint(L"CScrollBarWindow::_WindowProcCallback() : WM_PAINT\n");
            HDC dcHandle = nullptr;
            PAINTSTRUCT ps;

            dcHandle = BeginPaint(wndHandle, &ps);

            // paint itself at first
            _OnPaint(dcHandle, &ps);

            // paint all children
            if(_pBtnUp)
				_pBtnUp->_OnPaint(dcHandle, &ps);
            if(_pBtnDn)
				_pBtnDn->_OnPaint(dcHandle, &ps);

            EndPaint(wndHandle, &ps);
			ReleaseDC(wndHandle, dcHandle);
        }
        return 0;
    }

    return DefWindowProc(wndHandle, uMsg, wParam, lParam);
}

//+---------------------------------------------------------------------------
//
// _OnPaint
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_OnPaint(_In_ HDC dcHandle, _In_ PAINTSTRUCT *pps)
{
	debugPrint(L"CScrollBarWindow::_OnPaint()\n");
    HBRUSH hBrush = nullptr;
    CBaseWindow* pUIWnd = _GetUIWnd();

    if (pUIWnd && pUIWnd->_GetWnd())
    {
        hBrush = (HBRUSH)DefWindowProc(pUIWnd->_GetWnd(), WM_CTLCOLORSCROLLBAR, (WPARAM)dcHandle, (LPARAM)pUIWnd->_GetWnd());
    }

    if (hBrush == nullptr)
    {
        hBrush = GetSysColorBrush(COLOR_SCROLLBAR);
    }

	if(&pps)
		FillRect(dcHandle, &pps->rcPaint, hBrush);

	if(hBrush) DeleteObject(hBrush);
}

//+---------------------------------------------------------------------------
//
// _OnLButtonDown
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_OnLButtonDown(POINT pt)
{
    _StartCapture();

    RECT rc = {0, 0, 0, 0};

    if(_pBtnUp)
		_pBtnUp->_GetClientRect(&rc);
    if (_pBtnUp && PtInRect(&rc, pt))
    {
        _pBtnUp->_OnLButtonDown(pt);
    }
    else
    {
		if(_pBtnDn)
			_pBtnDn->_GetClientRect(&rc);
        if (_pBtnDn && PtInRect(&rc, pt))
        {
            _pBtnDn->_OnLButtonDown(pt);
        }
    }
}

//+---------------------------------------------------------------------------
//
// _OnLButtonUp
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_OnLButtonUp(POINT pt)
{
    if (_IsCapture())
    {
        CBaseWindow* pUIWnd = _GetTopmostUIWnd();
        if (pUIWnd)
        {
            CBaseWindow *pCapture = pUIWnd->_GetCaptureObject();
            if (pCapture && pCapture != this)
            {
                pCapture->_OnLButtonUp(pt);
            }
        }
    }
    else
    {
        RECT rc = {0, 0, 0, 0};

        if(_pBtnUp)
			_pBtnUp->_GetClientRect(&rc);
        if (_pBtnUp && PtInRect(&rc, pt))
        {
            _pBtnUp->_OnLButtonUp(pt);
        }
        else
        {
			if(_pBtnDn)
				_pBtnDn->_GetClientRect(&rc);
            if (_pBtnDn && PtInRect(&rc, pt))
            {
                _pBtnDn->_OnLButtonUp(pt);
            }
        }
    }

    if (_IsCapture())
    {
        _EndCapture();
    }
    if (_IsTimer())
    {
        _EndTimer();
    }

    _scrollDir = SCROLL_NONE_DIR;
    _InvalidateRect();
}

//+---------------------------------------------------------------------------
//
// _OnMouseMove
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_OnMouseMove(POINT pt)
{
    RECT rc = {0, 0, 0, 0};

	if(_pBtnUp)
		_pBtnUp->_GetClientRect(&rc);
    if (_pBtnUp && PtInRect(&rc, pt))
    {
        _pBtnUp->_OnMouseMove(pt);
    }
    else
    {
		if(_pBtnDn)
			_pBtnDn->_GetClientRect(&rc);
        if (_pBtnDn && PtInRect(&rc, pt))
        {
            _pBtnDn->_OnMouseMove(pt);
        }
    }
}

//+---------------------------------------------------------------------------
//
// _OnOwnerWndMoved
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_OnOwnerWndMoved(BOOL isResized)
{
    isResized;

    if (IsWindow(_GetWnd()) && IsWindowVisible(_GetWnd()))
    {
        _AdjustWindowPos();
    }
}

//+---------------------------------------------------------------------------
//
// _Resize
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_Resize(int x, int y, int cx, int cy)
{
    CBaseWindow::_Resize(x, y, cx, cy);

    RECT rc = {0, 0, 0, 0};

    _GetBtnUpRect(&rc);
    //_pBtnUp->_Resize(x, y+rc.top, rc.right-rc.left, rc.bottom-rc.top);
	if(_pBtnUp)
		_pBtnUp->_Resize(rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top);

    _GetBtnDnRect(&rc);
    //_pBtnDn->_Resize(x, y+rc.top, rc.right-rc.left, rc.bottom-rc.top);
	if(_pBtnDn)
		_pBtnDn->_Resize(rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top);
}

//+---------------------------------------------------------------------------
//
// _Show
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_Show(BOOL isShowWnd)
{
    if (_IsWindowVisible() != isShowWnd)
    {
        CBaseWindow::_Show(isShowWnd);

        if(_pBtnUp)
			_pBtnUp->_Show(isShowWnd);
        if(_pBtnDn)
			_pBtnDn->_Show(isShowWnd);
    }
}

//+---------------------------------------------------------------------------
//
// _Enable
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_Enable(BOOL isEnable)
{
	debugPrint(L"CScrollBarWindow::_Enable(), isEnable = %d, _scrollInfo.nMax = %d, _scrollInfo.nPage =%d, _scrollInfo.nPos = %d ",
		isEnable, _scrollInfo.nMax, _scrollInfo.nPage, _scrollInfo.nPos);
    if (_IsEnabled() != isEnable)
    {
        CBaseWindow::_Enable(isEnable);
    }
	if (_scrollInfo.nMax > _scrollInfo.nPage)
	{
		if (_pBtnUp)
		{
			BOOL upEnable = _scrollInfo.nPos >= _scrollInfo.nPage;
			debugPrint(L"CScrollBarWindow::_Enable() upEnable =%d", upEnable);
			_pBtnUp->_Enable(upEnable);
		}
		if (_pBtnDn)
		{
			int lastPageCount = _scrollInfo.nMax % _scrollInfo.nPage;
			if (lastPageCount == 0)
				lastPageCount = _scrollInfo.nPage;
			BOOL downEnable = _scrollInfo.nPos < _scrollInfo.nMax - lastPageCount;
			debugPrint(L"CScrollBarWindow::_Enable() downEnable =%d, lastPageCount = %d", downEnable, lastPageCount);
			_pBtnDn->_Enable(downEnable);
		}
	}
}

//+---------------------------------------------------------------------------
//
// _AdjustWindowPos
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_AdjustWindowPos()
{
    if (!IsWindow(_GetWnd()))
    {
        return;
    }

    RECT rc = {0, 0, 0, 0};
    CBaseWindow* pParent = _GetParent();
    if (pParent == nullptr)
    {
        return;
    }

    GetWindowRect(pParent->_GetWnd(), &rc);
	SetWindowPos(_GetWnd(), pParent->_GetWnd(),
		rc.left + (rc.right - rc.left) - GetSystemMetrics(SM_CXVSCROLL) * 3/2 - CANDWND_BORDER_WIDTH,
        rc.top + CANDWND_BORDER_WIDTH,
        
		GetSystemMetrics(SM_CXVSCROLL) *3/ 2,
		rc.bottom - rc.top - CANDWND_BORDER_WIDTH * 2,
        SWP_NOOWNERZORDER | SWP_NOACTIVATE);
}

//+---------------------------------------------------------------------------
//
// _SetScrollInfo
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_SetScrollInfo(_In_ CScrollInfo *lpsi)
{
    _scrollInfo = *lpsi;

    BOOL isEnable = (_scrollInfo.nMax > _scrollInfo.nPage);


    _scrollDir = SCROLL_NONE_DIR;

    _SetCurPos(_scrollInfo.nPos, -1);

	_Enable(isEnable);
}

//+---------------------------------------------------------------------------
//
// _GetScrollInfo
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_GetScrollInfo(_Out_ CScrollInfo *lpsi)
{
    *lpsi = _scrollInfo;
}

//+---------------------------------------------------------------------------
//
// _GetBtnUpRect
//
//----------------------------------------------------------------------------

BOOL CScrollBarWindow::_GetBtnUpRect(_Out_ RECT *prc)
{
    RECT rc = {0, 0, 0, 0};

    _GetClientRect(&rc);

    if (prc == nullptr)
    {
        return FALSE;
    }

    prc->left = rc.left;
    prc->top = rc.top;
    prc->right = rc.right;
    prc->bottom = rc.top + min(_sizeOfScrollBtn.cy, (rc.bottom - rc.top)/2);

    return TRUE;
}

//+---------------------------------------------------------------------------
//
// _GetBtnDnRect
//
//----------------------------------------------------------------------------

BOOL CScrollBarWindow::_GetBtnDnRect(_Out_ RECT *prc)
{
    RECT rc = {0, 0, 0, 0};

    _GetClientRect(&rc);

    if (prc == nullptr)
    {
        return FALSE;
    }

    prc->left = rc.left; 
    prc->top = rc.bottom - min(_sizeOfScrollBtn.cy, (rc.bottom - rc.top)/2);
    prc->right = rc.right;
    prc->bottom = rc.bottom;

    return TRUE;
}

//+---------------------------------------------------------------------------
//
// _GetScrollArea
//
//----------------------------------------------------------------------------

void CScrollBarWindow::_GetScrollArea(_Out_ RECT *prc)
{
    RECT rcBtnUp = {0, 0, 0, 0};
    RECT rcBtnDn = {0, 0, 0, 0};

    _GetBtnUpRect(&rcBtnUp);
    _GetBtnDnRect(&rcBtnDn);

    RECT rc = {0, 0, 0, 0};

    _GetClientRect(&rc);

    if (prc == nullptr)
    {
        return;
    }

    prc->left = rc.left;
    prc->top = rc.top + (rcBtnUp.bottom - rcBtnUp.top);
    prc->right = rc.right;
    prc->bottom = rc.bottom - (rcBtnDn.bottom - rcBtnDn.top);
}

//+---------------------------------------------------------------------------
//
// _SetCurPos
//
// param: dwSB - SB_xxx for WM_VSCROLL or WM_HSCROLL.
//    if -1 is specified, function doesn't send WM_xSCROLL message
//----------------------------------------------------------------------------

void CScrollBarWindow::_SetCurPos(int nPos, int dwSB)
{
	debugPrint(L"CScrollButtonWindow::_SetCurPos() nPos = %d, dwSB = %d", nPos, dwSB);
    //int posMax = (_scrollInfo.nMax <= _scrollInfo.nPage) ? 0 : _scrollInfo.nMax - _scrollInfo.nPage;

	nPos = min(nPos, _scrollInfo.nMax-1);// posMax);
    nPos = max(nPos, 0);

    _scrollInfo.nPos = nPos;

	BOOL enabled = _scrollInfo.nMax > _scrollInfo.nPage;
	_Enable(enabled);

    if (enabled && _IsWindowVisible())
	{
        _InvalidateRect();
    }

    if ((_IsCapture() && dwSB != -1) || (dwSB == SB_THUMBPOSITION))
    {
        _NotifyCommand(WM_VSCROLL, dwSB, nPos);
    }
}

//////////////////////////////////////////////////////////////////////
//
// CScrollButtonWindow class.
//
//////////////////////////////////////////////////////////////////////+---------------------------------------------------------------------------
//
// _OnPaint
//
//----------------------------------------------------------------------------

void CScrollButtonWindow::_OnPaint(_In_ HDC dcHandle, _In_ PAINTSTRUCT *pps)
{
	debugPrint(L"CScrollButtonWindow::_OnPaint()\n");
    pps;

    RECT rc = {0, 0, 0, 0};

    _GetClientRect(&rc);

    DrawFrameControl(dcHandle, &rc, DFC_SCROLL, DFCS_FLAT | subTypeOfControl | typeOfControl | (!_IsEnabled() ? DFCS_INACTIVE : 0));
}

//+---------------------------------------------------------------------------
//
// _OnLButtonDown
//
//----------------------------------------------------------------------------

void CScrollButtonWindow::_OnLButtonDown(POINT pt)
{
    CButtonWindow::_OnLButtonDown(pt);

    CScrollBarWindow* pParent = (CScrollBarWindow*)_GetParent();    // more secure w/ dynamic_cast
    if (pParent == nullptr)
    {
        return;
    }

    switch (subTypeOfControl)
    {
    case DFCS_SCROLLDOWN:
        _NotifyCommand(WM_VSCROLL, SB_PAGEDOWN, 0);
        pParent->_ShiftPage(+1, FALSE);
        break;
    case DFCS_SCROLLUP:
        _NotifyCommand(WM_VSCROLL, SB_PAGEUP, 0);
        pParent->_ShiftPage(-1, FALSE);
        break;
    }

    _StartTimer(_GetScrollDelay());
}

//+---------------------------------------------------------------------------
//
// _OnLButtonUp
//
//----------------------------------------------------------------------------

void CScrollButtonWindow::_OnLButtonUp(POINT pt)
{
    CButtonWindow::_OnLButtonUp(pt);

    _InvalidateRect();

    if (_IsTimer())
    {
        _EndTimer();
    }
}

//+---------------------------------------------------------------------------
//
// _OnTimer : Speed up page Down/Up while holding Down/Up Button
//
//----------------------------------------------------------------------------

void CScrollButtonWindow::_OnTimer()
{
    POINT pt = {0, 0};
    CScrollBarWindow* pParent = (CScrollBarWindow*)_GetParent();    // more secure w/ dynamic_cast
    if (pParent == nullptr)
    {
        return;
    }

    // start another faster timer
    _StartTimer(_GetScrollSpeed());

    GetCursorPos(&pt);
    MapWindowPoints(HWND_DESKTOP, pParent->_GetWnd(), &pt, 1);

    RECT rc = {0, 0, 0, 0};

    _GetClientRect(&rc);

    if (PtInRect(&rc, pt))
    {
        switch (subTypeOfControl)
        {
        case DFCS_SCROLLDOWN:
            _NotifyCommand(WM_VSCROLL, SB_LINEDOWN, 0);
            pParent->_ShiftLine(+1, FALSE);
            break;
        case DFCS_SCROLLUP:
            _NotifyCommand(WM_VSCROLL, SB_LINEUP, 0);
            pParent->_ShiftLine(-1, FALSE);
            break;
        }
    }
}